package com.taomus.mytools.simplec.compiler;

import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;

import org.mockito.asm.Opcodes;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;

import com.taomus.mytools.simplec.common.SyntaxTuple;
import com.taomus.mytools.simplec.common.WordEnum;
import com.taomus.mytools.simplec.utils.AsmUtils;
import com.taomus.mytools.simplec.utils.ErrorUtils;
import com.taomus.mytools.simplec.utils.GeneratorAdapterUtils;
import com.taomus.mytools.simplec.utils.ICode;
import static com.taomus.mytools.simplec.common.SyntaxTuple.*;

public class FunctionCompiler implements ICode{
	AsmUtils as;
	Queue<SyntaxTuple> tupleQ;
	Map<String,Integer> localNum = new HashMap<>();
	Map<String,Label> labelMap = new HashMap<>();
	public FunctionCompiler(final AsmUtils as,final Queue<SyntaxTuple> q){
		this.as = as;
		this.tupleQ = q;
	}
	@Override
	public void code(GeneratorAdapterUtils adapter) {
		int fAgrsNum = 0;
		while(true){
			SyntaxTuple st = tupleQ.poll();
			ErrorUtils.error(st);
			if(st.getOpCode() == WordEnum.FUNCR){
				break;
			}
			switch(st.getOpCode()){
			case FAGRSNUM:
				fAgrsNum = Integer.parseInt(st.getSymbol(T1));
				adapter.loadThis();
				adapter.push(fAgrsNum);
				adapter.newArray(Type.getType(Object.class));
				adapter.dup();
				break;
			case FRAGRS:
				for(int i =0;i<fAgrsNum;i++){
					adapter.push(i);
					if(st.getCode(T1) == WordEnum.NUMBER){
						adapter.push(Integer.parseInt(st.getSymbol(T1)));
						adapter.invokeStatic(Integer.class,"Integer valueOf(int)");
						adapter.arrayStore(Type.getType(Object.class));
						if(i+1 != fAgrsNum){
							adapter.dup();
						}
					}else if(st.getCode(T1) == WordEnum.ID){
						String fieldName = st.getSymbol(T1);
						switch(st.getToken(T1).getLevel()){
						case 0:
							adapter.loadThis();
							adapter.getField(fieldName, int.class);
							adapter.invokeStatic(Integer.class,"Integer valueOf(int)");
							adapter.arrayStore(Type.getType(Object.class));
							break;
						default:
							adapter.loadLocal(localNum.get(fieldName), int.class);
							adapter.invokeStatic(Integer.class,"Integer valueOf(int)");
							adapter.arrayStore(Type.getType(Object.class));
							break;
						}
						if(i+1 != fAgrsNum){
							adapter.dup();
						}
					}
					SyntaxTuple temp = tupleQ.peek(); 
					if(temp.getOpCode() == WordEnum.FRAGRS){
						st = tupleQ.poll();
					}
				}
				break;
			case FCALL:
				StringBuilder method = new StringBuilder("void ");
				method.append(st.getSymbol(T1));
				method.append("(Object[])");
				adapter.invokeVirtual(method.toString());
				break;
			case MOV:
				this.mov(st,adapter);
				break;
			case ADD:
			case MUL:
			case DIV:
			case SUB:
			case MOD:
				this.szys(st, adapter);
				break;
			case GE:
			case LE:
			case NE:
			case EQ:
			case LT:
			case GT:
				this.gt(st, adapter,st.getOpCode());
				break;
			case IFNE:
				this.ifne(st,adapter);
				break;
			case PRINT:
				this.print(st, adapter);
				break;
			case GOTO:
				this._goto(st, adapter);
				break;
			case IFELSE:
				this.ifelse(st, adapter);
				break;
			case IFEND:
				this.ifend(st, adapter);
				break;
			default:
				break;
			}
		}
	}
	private void szys(SyntaxTuple st,GeneratorAdapterUtils adapter){
		if(st.getToken(T1).getLevel() == 0){
			adapter.loadThis();
			adapter.getField(st.getSymbol(T1), int.class);
		}else{
			adapter.loadLocal(localNum.get(st.getSymbol(T1)),int.class);
		}
		if(st.getToken(T2).getLevel() == 0){
			adapter.loadThis();
			adapter.getField(st.getSymbol(T2), int.class);
		}else{
			adapter.loadLocal(localNum.get(st.getSymbol(T2)),int.class);
		}
		switch(st.getOpCode()){
		case ADD:
			adapter.visitInsn(Opcodes.IADD);
			break;
		case SUB:
			adapter.visitInsn(Opcodes.ISUB);
			break;
		case MUL:
			adapter.visitInsn(Opcodes.IMUL);
			break;
		case DIV:
			adapter.visitInsn(Opcodes.IDIV);
			break;
		default:
			break;
		}
		int local = adapter.newLocal(int.class);
		adapter.storeLocal(local, int.class);
		localNum.put(st.getSymbol(T3),local);
	}
	private void print(SyntaxTuple st,GeneratorAdapterUtils adapter){
		adapter.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class));
		if(st.getToken(T1).getLevel() == 0){
			adapter.loadThis();
			adapter.getField(st.getSymbol(T1), int.class);
		}else{
			adapter.loadLocal(localNum.get(st.getSymbol(T1)),int.class);
		}
		adapter.invokeVirtual(Type.getType(PrintStream.class),Method.getMethod("void println(int)"));
	}
	private void _goto(SyntaxTuple st,GeneratorAdapterUtils adapter){
		Label label = adapter.newLabel();
		adapter.goTo(label);
		labelMap.put(st.getSymbol(T1), label);
	}
	private void ifend(SyntaxTuple st,GeneratorAdapterUtils adapter){
		Label endLabel = labelMap.get(st.getSymbol(T1));
		labelMap.remove(st.getSymbol(T1));
		adapter.visitLabel(endLabel);
	}
	private void ifelse(SyntaxTuple st,GeneratorAdapterUtils adapter){
		ErrorUtils.error(st.getSymbol(T1));
		Label elseLabel = labelMap.get(st.getSymbol(T1));
		labelMap.remove(st.getSymbol(T1));
		adapter.visitLabel(elseLabel);
	}
	private void ifne(SyntaxTuple st,GeneratorAdapterUtils adapter){
		int loadtemp = localNum.get(st.getSymbol(T1));
		adapter.loadLocal(loadtemp, boolean.class);
		Label label = adapter.newLabel();
		adapter.ifZCmp(Opcodes.IFNE, label);
		labelMap.put(st.getSymbol(T3),label);
		ErrorUtils.error(st.getSymbol(T3));
	}
	
	private void gt(SyntaxTuple st,GeneratorAdapterUtils adapter,WordEnum word){
		if(st.getCode(T1) == WordEnum.ID){
			String fieldName = st.getSymbol(T1);
			if(st.getToken(T1).getLevel() == 0){
				adapter.loadThis();
				adapter.getField(fieldName,int.class);
			}else{
				adapter.loadLocal(localNum.get(fieldName),int.class);
			}
		}else if(st.getCode(T1) == WordEnum.NUMBER){
			adapter.push(Integer.parseInt(st.getSymbol(T1)));
		}
		if(st.getCode(T2) == WordEnum.ID){
			String fieldName = st.getSymbol(T2);
			if(st.getToken(T2).getLevel() == 0){
				adapter.loadThis();
				adapter.getField(fieldName,int.class);
			}else{
				adapter.loadLocal(localNum.get(fieldName),int.class);
			}
		}else if(st.getCode(T2) == WordEnum.NUMBER){
			adapter.push(Integer.parseInt(st.getSymbol(T3)));
		}
		Label l = adapter.newLabel();
		switch(word){
		case GT:
			adapter.ifICmp(Opcodes.IFLE, l);
			break;
		case LT:
			adapter.ifICmp(Opcodes.IFGE, l);
			break;
		case GE:
			adapter.ifICmp(Opcodes.IFLT, l);
			break;
		case LE:
			adapter.ifICmp(Opcodes.IFGT, l);
			break;
		case EQ:
			adapter.ifICmp(Opcodes.IFNE, l);
			break;
		default:
			adapter.ifICmp(Opcodes.IFEQ, l);
			break;
		}
		adapter.visitInsn(Opcodes.ICONST_0);
		Label gtl = adapter.newLabel();
		adapter.goTo(gtl);
		adapter.visitLabel(l);
		adapter.visitInsn(Opcodes.ICONST_1);
		adapter.visitLabel(gtl);
		int storelocal = adapter.newLocal(boolean.class);
		adapter.storeLocal(storelocal, boolean.class);
		localNum.put(st.getSymbol(T3),storelocal);
	}
	
	private void mov(SyntaxTuple st,GeneratorAdapterUtils adapter){
		if(st.getCode(T3) == WordEnum.ID){
			String fieldName = st.getSymbol(T3);
			if(st.getToken(T3).getLevel() == 0){
				adapter.loadThis();
				adapter.getField(fieldName, int.class);
			}else{
				adapter.loadLocal(localNum.get(fieldName),int.class);
			}
		}else if(st.getCode(T3) == WordEnum.NUMBER){
			adapter.push(Integer.parseInt(st.getSymbol(T3)));
		}else if(st.getCode(T3) == WordEnum.TEMP){
			String fieldName = st.getSymbol(T3);
			adapter.loadLocal(localNum.get(fieldName),int.class);
		}else if(st.getCode(T3) == WordEnum.FAGRS) {
			adapter.loadArg(0);
			int arg = Integer.parseInt(st.getSymbol(T3).split("@")[1]);
			adapter.push(arg - 1);
			adapter.visitInsn(Opcodes.AALOAD);
			adapter.checkCast(Type.getType(Integer.class));
			adapter.invokeVirtual(Integer.class,"int intValue()");
		}
		String fieldName = st.getSymbol(T1);
		if(st.getToken(T1).getLevel() == 0){
			adapter.loadThis();
			adapter.putField(st.getSymbol(T1),int.class);
		}else{
			int local = adapter.newLocal(int.class);
			adapter.storeLocal(local,int.class);
			localNum.put(fieldName, local);
		}
	}
}
